//////////////////////////////////////////////////////////////////////////
//
// This file is a part of UKROP project.
// Copyright (c) Vladimir Gumenuk
//
// PROJECT:         Xort Custom Debug Engine
// FILE:            
// AUTHOR:          Vladimir Gumenuk
// DATE:            Dec-2012 -- Jan-2013
// MODIFICATIONS:
//
//////////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "XortDebugEngine_i.h"
#include "EventBase.h"
#include "dllmain.h"

static LPCSTR eventIID2Str( REFIID iid )
{
    if( iid == IID_IDebugEngineCreateEvent2 )    return "EngineCreateEvent2";
    if( iid == IID_IDebugProcessCreateEvent2 )   return "ProcessCreateEvent2";
    if( iid == IID_IDebugProgramCreateEvent2 )   return "ProgramCreateEvent2";
    if( iid == IID_IDebugLoadCompleteEvent2 )    return "LoadCompleteEvent2";
    if( iid == IID_IDebugEntryPointEvent2 )      return "EntryPointEvent2";
    if( iid == IID_IDebugBreakpointBoundEvent2 ) return "BreakpointBoundEvent2";
    if( iid == IID_IDebugBreakpointEvent2 )      return "BreakpointEvent2";
    if( iid == IID_IDebugProgramDestroyEvent2 )  return "ProgramDestroyEvent2";
    if( iid == IID_IDebugProcessDestroyEvent2 )  return "ProcessDestroyEvent2";
    if( iid == IID_IDebugBreakEvent2 )           return "BreakEvent2";
    if( iid == IID_IDebugOutputStringEvent2 )    return "OutputStringEvent2";
    if( iid == IID_IDebugActivateDocumentEvent2 )return "ActivateDocumentEvent2";
    if( iid == IID_IDebugStepCompleteEvent2 )    return "StepCompleteEvent2";
    if( iid == IID_IDebugCanStopEvent2 )         return "CanStopEvent2";
    if( iid == IID_IDebugExpressionEvaluationCompleteEvent2 ) return "EvaluationCompleteEvent2";
    return "UNKNOWN EVENT IID";
}

// IUnknown methods implementation macro
#define IMPLEMENT_IUNKNOWN(T, I) \
    ULONG T::AddRef() { return CEventBase::AddRef(); } \
    ULONG T::Release() { return CEventBase::Release(); } \
    HRESULT T::QueryInterface(REFIID riid, LPVOID *ppvObj) { \
    if (IID_##I == riid) { \
    *ppvObj = (I *)(this); AddRef(); return S_OK; \
    } \
    return CEventBase::QueryInterface(riid, ppvObj); \
} 

CEventBase::CEventBase( REFIID iid, DWORD dwAttrib )
{
    m_dwAttrib  = dwAttrib;
    m_riid      = iid;
    m_iRefCount = 0;
}

CEventBase::~CEventBase()
{
}

////////////////////////////////////////////////////////////////////////////// 
// IUnknown methods 
ULONG CEventBase::AddRef() 
{ 
    return ++m_iRefCount; 
} 

ULONG CEventBase::Release() 
{ 
    if( --m_iRefCount == 0 ) { 
        delete this; 
        return 0; 
    } 
    return m_iRefCount; 
} 

HRESULT CEventBase::QueryInterface( REFIID riid, LPVOID *ppvObj ) 
{ 
    if (riid == IID_IUnknown) 
        *ppvObj = (IUnknown*) this; 
    else if (riid == IID_IDebugEvent2) 
        *ppvObj = (IDebugEvent2*) this; 
    else return E_NOINTERFACE; 

    AddRef(); 
    return S_OK;  
} 

////////////////////////////////////////////////////////////////////////////// 
// IDebugEvent2 methods 
HRESULT CEventBase::GetAttributes( DWORD* pdwAttrib ) 
{ 
    *pdwAttrib = m_dwAttrib; 
    return S_OK; 
} 

// helper: sends an event back to the session debug manager (SDM)
HRESULT CEventBase::Send()
{
    AddRef(); // ref+ on itself

    IDebugEngine2        *pEng  = _AtlModule.getEngineNoAddRef();
    IDebugProcess2       *pProc = _AtlModule.getProcessNoAddRef(); 
    IDebugProgram2       *pProg = _AtlModule.getProgramNoAddRef(); 
    IDebugThread2        *pTh   = _AtlModule.getThreadNoAddRef();
    IDebugEventCallback2 *pCb   = _AtlModule.getCallbackNoAddRef();

    //ATLTRACE( "SEND [%s] : 0x%08X -> DE: 0x%08X, PROCESS: 0x%08X, PROGRAM: 0x%08X, THREAD: 0x%08X\n",
    //    eventIID2Str( m_riid ), m_dwAttrib, pEng, pProc, pProg, pTh );

    // Send the event to the SDM
    HRESULT hr = pCb->Event( pEng, pProc, pProg, pTh, (IDebugEvent2 *) this, m_riid, m_dwAttrib );

    // If event is synchronous, pump messages and wait for continuation.
    if( SUCCEEDED( hr ) )
    {
        if( m_dwAttrib & EVENT_SYNCHRONOUS )
        {
            //ATLTRACE( "...waiting for WM_CONTINUE_SYNC_EVENT...\n" );
            MSG msg;
            while( GetMessage( &msg, NULL, 0, 0 ) )
            {
                if( msg.message == WM_CONTINUE_SYNC_EVENT ) {
                    //ATLTRACE( "...wait OK\n" );
                    break;
                }

                DispatchMessage( &msg );
            }
        }
    }

    //ATLTRACE( "CEventBase::Send() -> LEAVE, hr: 0x%08X\n", hr );
    Release(); // ref- on itself - and typically kills itself :)
    return hr;
}

//////////////////////////////////////////////////////////////////////////
// CEngineCreateEvent
CEngineCreateEvent::CEngineCreateEvent( IDebugEngine2 *pEngine )
    : CEventBase( IID_IDebugEngineCreateEvent2, EVENT_IMMEDIATE ) // was async
{
    m_spEngine = pEngine; 
}

CEngineCreateEvent::~CEngineCreateEvent()
{

}   

IMPLEMENT_IUNKNOWN( CEngineCreateEvent, IDebugEngineCreateEvent2 )

////////////////////////////////////////////////////////////////////////////// 
// IDebugEngineCreateEvent2 
HRESULT CEngineCreateEvent::GetEngine(IDebugEngine2 **ppEngine) 
{ 
    *ppEngine = m_spEngine; 
    (*ppEngine)->AddRef(); 
    return S_OK; 
} 

//////////////////////////////////////////////////////////////////////////
//
CProcessCreateEvent::CProcessCreateEvent()
    : CEventBase( IID_IDebugProcessCreateEvent2, EVENT_IMMEDIATE ) 
{

}

CProcessCreateEvent::~CProcessCreateEvent()
{

}

IMPLEMENT_IUNKNOWN( CProcessCreateEvent, IDebugProcessCreateEvent2 )

CProcessDestroyEvent::CProcessDestroyEvent()
: CEventBase( IID_IDebugProcessDestroyEvent2, EVENT_IMMEDIATE ) 
{

}

CProcessDestroyEvent::~CProcessDestroyEvent()
{

}

IMPLEMENT_IUNKNOWN( CProcessDestroyEvent, IDebugProcessDestroyEvent2 )

///////////////////////////////////////////////////////////////////////////
//
CProgramCreateEvent::CProgramCreateEvent()
    : CEventBase( IID_IDebugProgramCreateEvent2, EVENT_IMMEDIATE ) 
{

}

CProgramCreateEvent::~CProgramCreateEvent()
{

}

IMPLEMENT_IUNKNOWN( CProgramCreateEvent, IDebugProgramCreateEvent2 )
IMPLEMENT_IUNKNOWN( CLoadCompleteEvent, IDebugLoadCompleteEvent2 )
IMPLEMENT_IUNKNOWN( CEntryPointEvent, IDebugEntryPointEvent2 )

//////////////////////////////////////////////////////////////////////////
//
CBreakpointBoundEvent::CBreakpointBoundEvent( IEnumDebugBoundBreakpoints2 *pEnum, IDebugPendingBreakpoint2 *pPending ) 
    : CEventBase(IID_IDebugBreakpointBoundEvent2, EVENT_ASYNCHRONOUS) 
{
    m_spEnum = pEnum; 
    m_spPending = pPending; 
}


////////////////////////////////////////////////////////////////////////////// 
// IUnknown 
IMPLEMENT_IUNKNOWN(CBreakpointBoundEvent, IDebugBreakpointBoundEvent2) 

////////////////////////////////////////////////////////////////////////////// 
// IDebugBreakpointBoundEvent2 
HRESULT CBreakpointBoundEvent::GetPendingBreakpoint(IDebugPendingBreakpoint2** ppPendingBP) 
{ 
    // Get the pending breakpoint that was bound to the breakpoint 
    *ppPendingBP = m_spPending; 
    (*ppPendingBP)->AddRef(); 
    return S_OK; 
} 

HRESULT CBreakpointBoundEvent::EnumBoundBreakpoints(IEnumDebugBoundBreakpoints2** ppEnum) 
{ 
    // Get the list of breakpoints bound on this event 
    *ppEnum = m_spEnum; 
    (*ppEnum)->AddRef(); 
    return S_OK; 
} 

//////////////////////////////////////////////////////////////////////////
//
typedef CComEnumWithCount< IEnumDebugBoundBreakpoints2, &IID_IEnumDebugBoundBreakpoints2, 
    IDebugBoundBreakpoint2*, _CopyInterface<IDebugBoundBreakpoint2> > CEnumDebugBoundBreakpoints; 

IMPLEMENT_IUNKNOWN(CCodeBreakpointEvent, IDebugBreakpointEvent2) 

HRESULT CCodeBreakpointEvent::EnumBreakpoints( IEnumDebugBoundBreakpoints2 **ppEnum ) 
{ 
    ATLTRACE( "CCodeBreakpointEvent::EnumBreakpoints ENTER\n" );
    
    // Create the bound enumerator 
    CComObject<CEnumDebugBoundBreakpoints>* pBoundEnum; 
    HRESULT hr = CComObject<CEnumDebugBoundBreakpoints>::CreateInstance( &pBoundEnum ); 
    if( SUCCEEDED( hr ) )
    {
        IDebugBoundBreakpoint2* rgpBoundBP[] = { m_spBP }; 
        if( SUCCEEDED( hr = pBoundEnum->Init( rgpBoundBP, &(rgpBoundBP[1]), NULL, AtlFlagCopy ) ) )
        {
            hr = pBoundEnum->QueryInterface( IID_IEnumDebugBoundBreakpoints2, (void**)ppEnum );
        }
    }

    ATLTRACE( "CCodeBreakpointEvent::EnumBreakpoints() -> LEAVE, hr: 0x%08X\n", hr );
    return S_OK; 
} 

IMPLEMENT_IUNKNOWN(CProgramDestroyEvent, IDebugProgramDestroyEvent2) 
IMPLEMENT_IUNKNOWN(CDebugBreakEvent, IDebugBreakEvent2) 
IMPLEMENT_IUNKNOWN(COutputStringEvent, IDebugOutputStringEvent2)
IMPLEMENT_IUNKNOWN(CActivateDocumentEvent, IDebugActivateDocumentEvent2)
IMPLEMENT_IUNKNOWN(CStepCompleteEvent, IDebugStepCompleteEvent2);
IMPLEMENT_IUNKNOWN(CCanStopEvent, IDebugCanStopEvent2);
IMPLEMENT_IUNKNOWN(CExpressionEvaluationCompleteEvent, IDebugExpressionEvaluationCompleteEvent2);
